package com.informatics.polymer.server.Polymerisation;

import java.io.IOException;
import java.util.ArrayList;

import nu.xom.Builder;
import nu.xom.Document;
import nu.xom.ParsingException;
import nu.xom.ValidityException;

import org.xmlcml.cml.element.CMLAtom;
import org.xmlcml.cml.element.CMLBond;
import org.xmlcml.cml.element.CMLBondArray;
import org.xmlcml.cml.element.CMLFragment;
import org.xmlcml.cml.element.CMLJoin;
import org.xmlcml.cml.element.CMLMolecule;

/**
 * Class to create alternating copolymers.<br>
 * </br> <b>Example</b><br>
 * </br> AlternatingCopolymerisation alternatingCopolymerisation = new
 * AlternatingCopolymerisation
 * (3,1,1,45.0,"Br[R]","[R]c1cccc(C[R])c1","[R]OCC1CCCC(C[R])C1","Cl[R]");<br>
 * </br> String twoDimensionalPolymer =
 * alternatingCopolymerisation.getPolymer().toXML();<br>
 * </br> String threeDimensionalPolymer =
 * alternatingCopolymerisation.add3DCoordinates(twoDimensionalPolymer);<br>
 * </br> <b>Note</b><br>
 * </br> Coordinate generation is done using external dependencies, from the
 * Chemistry Development kit<br>
 * </br> <a href="http://pele.farmbio.uu.se/nightly/api/org/openscience/cdk/modeling/builder3d/ModelBuilder3D.html"
 * >ModelBuilder3D class</a>.
 * 
 * @author Ed Cannon
 * @version 1.0
 */
public class AlternatingCopolymerisation implements Polymerisation {
	/**
	 * int representing the number of repeat unit molecules of type A.
	 */
	private int numberOfAUnits;
	/**
	 * int representing the number of repeat unit molecules of type B.
	 */
	private int numberOfBUnits;
	/**
	 * int representing the number of occurrences of the AB repeat (AAABBB)x.
	 */
	private int degreeOfPolymerisation;
	/**
	 * double, representing the torsion angle between separate repeat units.
	 */
	private double torsionAngle;
	/**
	 * CMLMolecule representation of end group 1.
	 */
	private CMLMolecule endGroup1Molecule;
	/**
	 * CMLMolecule representation of repeat unit A.
	 */
	private CMLMolecule repeatUnitAMolecule;
	/**
	 * CMLMolecule representation of repeat unit B.
	 */
	private CMLMolecule repeatUnitBMolecule;
	/**
	 * CMLMolecule representation of end group 2.
	 */
	private CMLMolecule endGroup2Molecule;
	/**
	 * CMLMolecule representation of the alternating copolymer.
	 */
	private CMLMolecule alternatingCopolymer;
	/**
	 * Maximum torsion angle allowed.
	 */
	private static final int TORSION_ANGLE_UPPER_LIMIT = 360;
	/**
	 * Parent fragment for pml generation.
	 */
	private CMLFragment parentFragment;

	/**
	 * Constructs an object to undertake alternating copolymerisation, no fields
	 * are set.
	 */
	public AlternatingCopolymerisation() {
	}

	/**
	 * Constructs an object to undertake alternating copolymerisation, using the
	 * number of units of block A, the number of units of block B, the degree of
	 * polymerisation, torsion angle between the blocks (at present not used)
	 * and a string representation for end group 1 and 2 and repeat units A and
	 * B. Currently restricted to two different blocks (A & B) only. End groups
	 * are optional, if not set the default is [R][H].
	 * 
	 * @param unitsOfA
	 *            - int representing the number of repeat unit molecules of type
	 *            A joined together before addition of a B unit i.e. AAAAB = 4
	 * @param unitsOfB
	 *            - int representing the number of repeat unit molecules of type
	 *            B joined together before addition of an A unit i.e BBBA = 3
	 * @param degreeOfPolymerisationValue
	 *            - int representing the number of occurrences of the AB repeat
	 *            unit e.g. (AAAA-BBB)x, where x = degreeOfPolymerisation
	 * @param torsionAngleValue
	 *            - double, representing the torsion angle between separate
	 *            repeat unit components
	 * @param endGroup1
	 *            - SMILE string representation of the end group 1 molecule used
	 *            as the starting point in the polymerisation.
	 * @param repeatUnitA
	 *            - SMILE string representation of repeat unit A
	 * @param repeatUnitB
	 *            - SMILE string representation of repeat unit B
	 * @param endGroup2
	 *            - SMILE string representation of the end group 2 molecule used
	 *            as the end point in the polymerisation.
	 * 
	 */
	public AlternatingCopolymerisation(final int unitsOfA, final int unitsOfB,
			final int degreeOfPolymerisationValue,
			final double torsionAngleValue, final String endGroup1,
			final String repeatUnitA, final String repeatUnitB,
			final String endGroup2) {
		checkParameterSetUp(unitsOfA, unitsOfB, degreeOfPolymerisationValue,
				torsionAngleValue);
		checkStringMoleculeParameterSetUp(endGroup1, repeatUnitA, repeatUnitB,
				endGroup2);
	}

	/**
	 * Constructs an object to undertake alternating copolymerisation, using the
	 * number of units of block A, the number of units of block B, the degree of
	 * polymerisation, torsion angle between the blocks (at present not used)
	 * and a CMLMolecule representation for end group 1 and 2 and repeat units A
	 * and B. Currently restricted to two different blocks (A & B) only. End
	 * groups are optional, if not set the default is [R][H].
	 * 
	 * @param unitsOfA
	 *            - int representing the number of repeat unit molecules of type
	 *            A joined together before addition of a B unit i.e. AAAAB = 4
	 * @param unitsOfB
	 *            - int representing the number of repeat unit molecules of type
	 *            B joined together before addition of an A unit i.e BBBA = 3
	 * @param degreeOfPolymerisationValue
	 *            - int representing the number of occurrences of the AB repeat
	 *            unit e.g. (AAAA-BBB)x, where x = degreeOfPolymerisation
	 * @param torsionAngleValue
	 *            - double, representing the torsion angle between separate
	 *            repeat unit components
	 * @param endGroup1
	 *            - CMLMolecule representation of the end group 1 molecule used
	 *            as the starting point in the polymerisation.
	 * @param repeatUnitA
	 *            - CMLMolecule representation of repeat unit A
	 * @param repeatUnitB
	 *            - CMLMolecule representation of repeat unit B
	 * @param endGroup2
	 *            - CMLMolecule representation of the end group 2 molecule used
	 *            as the end point in the polymerisation.
	 */
	public AlternatingCopolymerisation(final int unitsOfA, final int unitsOfB,
			final int degreeOfPolymerisationValue,
			final double torsionAngleValue, final CMLMolecule endGroup1,
			final CMLMolecule repeatUnitA, final CMLMolecule repeatUnitB,
			final CMLMolecule endGroup2) {
		checkParameterSetUp(unitsOfA, unitsOfB, degreeOfPolymerisationValue,
				torsionAngleValue);
		checkCMLMoleculeParameterSetUp(endGroup1, repeatUnitA, repeatUnitB,
				endGroup2);
	}

	/**
	 * Sets the number of units of A.
	 * 
	 * @param aUnits
	 *            - the number of units of repeat unit A
	 */
	public final void setNumberOfAUnits(final int aUnits) {
		this.numberOfAUnits = aUnits;
	}

	/**
	 * Sets the number of units of B.
	 * 
	 * @param bUnits
	 *            - the number of units of repeat unit B
	 */
	public final void setNumberOfBUnits(final int bUnits) {
		this.numberOfBUnits = bUnits;
	}

	/**
	 * Sets the degree of polymerisation.
	 * 
	 * @param degreeOfPolymerisationValue
	 *            - int representing the number of occurrences of the AB repeat
	 *            unit e.g. (AAAA-BBB)x, where x = degreeOfPolymerisation
	 */
	public final void setDegreeOfPolymerisation(
			final int degreeOfPolymerisationValue) {
		this.degreeOfPolymerisation = degreeOfPolymerisationValue;
	}

	/**
	 * Sets the torsion angle.
	 * 
	 * @param torsionAngleValue
	 *            - double representing the torsion angle between occurences of
	 *            the AB repeat unit
	 */
	public final void setTorsionAngle(final double torsionAngleValue) {
		this.torsionAngle = torsionAngleValue;
	}

	/**
	 * Sets end group 1.
	 * 
	 * @param endGroup1
	 *            - CMLMolecule representation to set end group 1
	 */
	public final void setEndGroup1(final CMLMolecule endGroup1) {
		this.endGroup1Molecule = endGroup1;
	}

	/**
	 * Sets repeat unit A.
	 * 
	 * @param repeatUnitA
	 *            - CMLMolecule representation of repeat unit A
	 */
	public final void setRepeatUnitA(final CMLMolecule repeatUnitA) {
		this.repeatUnitAMolecule = repeatUnitA;
	}

	/**
	 * Sets repeat unit B.
	 * 
	 * @param repeatUnitB
	 *            - CMLMolecule representation of repeat unit B
	 */
	public final void setRepeatUnitB(final CMLMolecule repeatUnitB) {
		this.repeatUnitBMolecule = repeatUnitB;
	}

	/**
	 * Sets end group 2.
	 * 
	 * @param endGroup2
	 *            - CMLMolecule representation of end group 2
	 */
	public final void setEndGroup2(final CMLMolecule endGroup2) {
		this.endGroup2Molecule = endGroup2;
	}

	/**
	 * Returns int representing the number of repeat unit molecules of type A
	 * joined together before addition of a B unit (AAAAB = 4).
	 * 
	 * @return numberOfAunits
	 */
	public final int getNumberOfAUnits() {
		return numberOfAUnits;
	}

	/**
	 * Returns int representing the number of repeat unit molecules of type B
	 * joined together before addition of an A unit (BBBA = 3).
	 * 
	 * @return numberOfBUnits
	 */
	public final int getNumberOfBUnits() {
		return numberOfBUnits;
	}

	/**
	 * Returns int representing the number of occurrences of the AB repeat unit
	 * (AAAA-BBB)x, where x = degreeOfPolymerisation.
	 * 
	 * @return degreeOfPolymerisation
	 */
	public final int getDegreeOfPolymerisation() {
		return degreeOfPolymerisation;
	}

	/**
	 * Returns double, representing the torsion angle between separate repeat
	 * unit components.
	 * 
	 * @return torsionAngle
	 */
	public final double getTorsionAngle() {
		return torsionAngle;
	}

	/**
	 * Returns CMLMolecule representation of the end group 1 molecule used as
	 * the starting point in the polymerisation.
	 * 
	 * @return endGroup1Molecule
	 */
	public final CMLMolecule getEndGroup1() {
		return endGroup1Molecule;
	}

	/**
	 * Returns CMLMolecule representation of repeat unit A.
	 * 
	 * @return repeatUnitAMolecule
	 */
	public final CMLMolecule getRepeatUnitA() {
		return repeatUnitAMolecule;
	}

	/**
	 * Returns CMLMolecule representation of repeat unit B.
	 * 
	 * @return repeatUnitBMolecule
	 */
	public final CMLMolecule getRepeatUnitB() {
		return repeatUnitBMolecule;
	}

	/**
	 * Returns CMLMolecule representation of the end group 2 molecule used as
	 * the end point in the polymerisation.
	 * 
	 * @return endGroup2Molecule
	 */
	public final CMLMolecule getEndGroup2() {
		return endGroup2Molecule;
	}

	/**
	 * Returns an XML string representation of the generated alternating
	 * copolymer.
	 * 
	 * @return alternatingCopolymer.toXML() (non-Javadoc)
	 * @see org.xmlcml.cml.tools.polymer.Polymerisation#generatePML()
	 */
	public final String generatePML() {
		return alternatingCopolymer.toXML();
	}

	/**
	 * Generates ArrayList<CMLMolecule> from the degree of polymerisation
	 * parameter, a CMLMolecule acting as the repeat unit and an integer value
	 * for the number of units.
	 * 
	 * @param dp
	 *            - int representing the number of occurrences of the AB repeat
	 *            unit e.g. (AAAA-BBB)x, where x = degreeOfPolymerisation
	 * @param m
	 *            - CMLMolecule representing the repeat unit to be polymerised
	 * @param numberOfUnits
	 *            - int representing the number of repeat unit molecules of type
	 *            X joined together before addition of a Y unit
	 * @param identifierValue
	 *            - String representing the unique identifier to be added to the
	 *            separate repeat units
	 * @return ArrayList<CMLMolecule>, essentially a list of unjoined
	 *         CMLMolecules ready to be polymerised
	 */
	private ArrayList<CMLMolecule> separateHomoRepeatUnits(final int dp,
			final CMLMolecule m, final int numberOfUnits,
			final String identifierValue) {
		ArrayList<CMLMolecule> polymerisedMolecules = new ArrayList<CMLMolecule>();
		for (int i = 0; i < dp; i++) {
			CMLMolecule newMolecule = new CMLMolecule();
			String identifierNewValue = Integer.toString(i) + identifierValue;
			newMolecule = PolymerisationUtils.countExpression(numberOfUnits, m,
					identifierNewValue);
			polymerisedMolecules.add(newMolecule);
		}
		return polymerisedMolecules;
	}

	/**
	 * . Takes an ArrayList<CMLMolecule> and an ArrayList of ids which are used
	 * to build bonds between the molecules and join into a polymer
	 * 
	 * @param molecules
	 *            - ArrayList<CMLMolecule>, the molecules to be joined by bonds.
	 * @param ids
	 *            - ArrayList<String>, a list of atom ids to be used in adding
	 *            bonds between the separated molecules
	 */
	private void joinMolecules(final ArrayList<CMLMolecule> molecules,
			final ArrayList<String> ids) {
		alternatingCopolymer = new CMLMolecule();
		for (int i = 0; i < molecules.size(); i++) {
			// Add the atoms
			PolymerisationUtils.addAtoms(molecules, i, alternatingCopolymer);
			// Now add the bonds
			PolymerisationUtils.addBonds(molecules, i, alternatingCopolymer);
		}
		// Need to add the linker bonds to the molecule now.
		// Need to form CMLBonds between Id 0->1, 2->3,4->5 etc i.e between atom
		// (i) and atom (i+1)
		for (int i = 0; i < ids.size(); i++) {
			if (i % 2 == 0) {
				CMLBond cb = new CMLBond();
				cb.setAttribute("atomRefs2", ids.get(i) + " " + ids.get(i + 1));
				cb.setId(ids.get(i) + " " + ids.get(i + 1));
				cb.setOrder("1");
				alternatingCopolymer.addBond(cb);
			}
		}
		// Add R-R delete method
		ArrayList<CMLBond> bothRAtoms = new ArrayList<CMLBond>();
		CMLBondArray rests = alternatingCopolymer.getBondArray();
		PolymerisationUtils.createNewMoleculeJoinedWithoutRRgroups(rests,
				bothRAtoms, alternatingCopolymer);
	}

	/**
	 * Add 3D coordinates to built 2D polymer XML representation. Note:
	 * Coordinate generation is done using an external dependencies, from the
	 * Chemistry Development kit <a href="http://pele.farmbio.uu.se/nightly/api/org/openscience/cdk/modeling/builder3d/ModelBuilder3D.html"
	 * >ModelBuilder3D class</a>.
	 * 
	 * @param pmlStringNoCoordinates
	 *            - 2D XML file, representing the built polymer.
	 * @return XML string representation of the polymer with 3D coordinates.
	 * @throws Exception
	 *             if 3D coordinate generation fails.
	 */
	public final String add3DCoordinates(final String pmlStringNoCoordinates)
			throws Exception {
		String molWithCoords = PolymerisationUtils
				.add3DCoordinates(pmlStringNoCoordinates);
		return molWithCoords;
	}

	/*
	 * public static void main(String[] args) throws Exception {
	 * AlternatingCopolymerisation alternatingCopolymerisation = new
	 * AlternatingCopolymerisation( 1, 1, 2, 45.0, "Br[R]", "[R]c1cccc(C[R])c1",
	 * "[R]OCC1CCCC(C[R])C1", "Cl[R]"); CMLFragment frag =
	 * alternatingCopolymerisation.getFragment(); Document pmlBasicDoc =
	 * PolymerisationUtils.createPMLBasic(frag); Document pmlIntermediateDoc =
	 * PolymerisationUtils.createPMLIntermediate(frag);
	 * System.out.println(pmlBasicDoc.toXML());
	 * System.out.println(pmlIntermediateDoc.toXML()); } //Document pmlBasicDoc
	 * = / alternatingCopolymerisation.getPolymer().toXML(); //Document d =
	 * alternatingCopolymerisation.asXML(); //System.out.println(d.toXML());
	 * System.out.println(result); String //threeDPolymer =
	 * alternatingCopolymerisation.add3DCoordinates(result); }
	 */

	/**
	 * Returns CMLMolecule representation of the built polymer.
	 * 
	 * @return alternatingCopolymer (non-Javadoc)
	 * @see org.xmlcml.cml.tools.polymer.Polymerisation#getPolymer()
	 */
	public final CMLMolecule getPolymer() {
		//boolean buildable = PolymerisationUtils.isBuildable(getFragment());
		boolean buildable = true;
		if (buildable == true) {
			ArrayList<CMLAtom> eg1R = PolymerisationUtils
					.getRElements(endGroup1Molecule);
			ArrayList<CMLAtom> eg2R = PolymerisationUtils
					.getRElements(endGroup2Molecule);
			ArrayList<ArrayList<CMLAtom>> repeatUnitARgroups = new ArrayList<ArrayList<CMLAtom>>();
			ArrayList<ArrayList<CMLAtom>> repeatUnitBRgroups = new ArrayList<ArrayList<CMLAtom>>();
			ArrayList<CMLAtom> repeatUnitARgroup;
			ArrayList<CMLAtom> repeatUnitBRgroup;
			ArrayList<CMLMolecule> repeatUnitAPolymerised = null;
			ArrayList<CMLMolecule> repeatUnitBPolymerised = null;
			if (this.numberOfAUnits == 1) {
				repeatUnitAPolymerised = PolymerisationUtils
						.getRepeatUnitMolecules(this.degreeOfPolymerisation,
								repeatUnitAMolecule, "RUA");
				for (int i = 0; i < degreeOfPolymerisation; i++) {
					repeatUnitARgroup = PolymerisationUtils
							.getRElements(repeatUnitAPolymerised.get(i));
					repeatUnitARgroups.add(repeatUnitARgroup);
				}
			} else {
				repeatUnitAPolymerised = separateHomoRepeatUnits(
						this.degreeOfPolymerisation, repeatUnitAMolecule,
						this.numberOfAUnits, "RUA");
				for (int i = 0; i < repeatUnitAPolymerised.size(); i++) {
					ArrayList<CMLAtom> rgroupsPerMolecule = PolymerisationUtils
							.getRElements(repeatUnitAPolymerised.get(i));
					repeatUnitARgroups.add(rgroupsPerMolecule);
				}
			}
			if (this.numberOfBUnits == 1) {
				repeatUnitBPolymerised = PolymerisationUtils
						.getRepeatUnitMolecules(this.degreeOfPolymerisation,
								repeatUnitBMolecule, "RUB");
				for (int i = 0; i < degreeOfPolymerisation; i++) {
					repeatUnitBRgroup = PolymerisationUtils
							.getRElements(repeatUnitBPolymerised.get(i));
					repeatUnitBRgroups.add(repeatUnitBRgroup);
				}

			} else {
				repeatUnitBPolymerised = separateHomoRepeatUnits(
						this.degreeOfPolymerisation, repeatUnitBMolecule,
						this.numberOfBUnits, "RUB");
				for (int i = 0; i < repeatUnitBPolymerised.size(); i++) {
					ArrayList<CMLAtom> rgroupsPerMolecule = PolymerisationUtils
							.getRElements(repeatUnitBPolymerised.get(i));
					repeatUnitBRgroups.add(rgroupsPerMolecule);
				}
			}
			ArrayList<CMLMolecule> combiningAandB = new ArrayList<CMLMolecule>();
			ArrayList<String> rrBondJoinIds = new ArrayList<String>();
			if (this.degreeOfPolymerisation > 1 && numberOfAUnits == 1) {
				combiningAandB.add(endGroup1Molecule);
				for (int i = 0; i < degreeOfPolymerisation; i++) {
					combiningAandB.add(repeatUnitAPolymerised.get(i));
					combiningAandB.add(repeatUnitBPolymerised.get(i));
				}
				combiningAandB.add(endGroup2Molecule);
				rrBondJoinIds.add(eg1R.get(0).getId());
				addRRBondsAndJoin(repeatUnitARgroups, rrBondJoinIds,
						repeatUnitBRgroups, combiningAandB, eg2R);
			} else if (degreeOfPolymerisation > 1 && numberOfBUnits == 1) {
				combiningAandB.add(endGroup1Molecule);
				for (int i = 0; i < degreeOfPolymerisation; i++) {
					combiningAandB.add(repeatUnitAPolymerised.get(i));
					combiningAandB.add(repeatUnitBPolymerised.get(i));
				}
				combiningAandB.add(endGroup2Molecule);
				rrBondJoinIds.add(eg1R.get(0).getId());
				addRRBondsAndJoin(repeatUnitARgroups, rrBondJoinIds,
						repeatUnitBRgroups, combiningAandB, eg2R);
			} else {
				// Need if statement if number of A units or B units when DP=1
				combiningAandB.add(endGroup1Molecule);
				for (int i = 0; i < repeatUnitAPolymerised.size(); i++) {
					combiningAandB.add(repeatUnitAPolymerised.get(i));
					combiningAandB.add(repeatUnitBPolymerised.get(i));
				}
				combiningAandB.add(endGroup2Molecule);
				rrBondJoinIds.add(eg1R.get(0).getId());
				addRRBondsAndJoin(repeatUnitARgroups, rrBondJoinIds,
						repeatUnitBRgroups, combiningAandB, eg2R);
			}
			return this.alternatingCopolymer;
		} else {
			UnbuildableException unbuildableException = new UnbuildableException(
					"Can't be built");
			unbuildableException.getMessage();
			return null;
		}
	}

	/**
	 * Method returns a fully atomistic CML Document representation of the
	 * polymer.
	 * 
	 * @return Document
	 */
	public final Document createCML() {
		Builder builder = new Builder();
		Document doc = null;
		try {
			doc = builder.build(alternatingCopolymer.toXML(), "");
		} catch (ValidityException e) {
			e.printStackTrace();
		} catch (ParsingException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
		return doc;
	}

	/**
	 * Checks parameters parsed into the constructor are correct.
	 * 
	 * @param unitsOfA
	 *            -int representing the number of repeat unit molecules of type
	 *            A joined together before addition of a B unit i.e. AAAAB = 4
	 * @param unitsOfB
	 *            - int representing the number of repeat unit molecules of type
	 *            B joined together before addition of an A unit i.e. BBBBA = 4.
	 * @param degreeOfPolymerisationValue
	 *            - int representing the number of occurrences of the AB repeat
	 *            unit e.g. (AAAA-BBB)x, where x = degreeOfPolymerisation
	 * @param torsionAnglevalue
	 *            - double representing the torsion angle between separate
	 *            repeat unit components
	 */
	private void checkParameterSetUp(final int unitsOfA, final int unitsOfB,
			final int degreeOfPolymerisationValue,
			final double torsionAnglevalue) {
		if (unitsOfA < 1) {
			throw new IllegalArgumentException(
					"Need to have an integer greater than 1 for the number of units of A!");
		} else {
			this.numberOfAUnits = unitsOfA;
		}
		if (unitsOfB < 1) {
			throw new IllegalArgumentException(
					"Need to have an integer greater than 1 for the number of units of A!");
		} else {
			this.numberOfBUnits = unitsOfB;
		}
		if (degreeOfPolymerisationValue < 1) {
			throw new IllegalArgumentException(
					"Need to have an integer degree of polymerisation greater than zero!");
		} else {
			this.degreeOfPolymerisation = degreeOfPolymerisationValue;
		}
		if (torsionAnglevalue < 0) {
			throw new IllegalArgumentException("Negative torsion angle!");
		} else if (torsionAnglevalue > TORSION_ANGLE_UPPER_LIMIT) {
			this.torsionAngle = Math.round(torsionAnglevalue
					/ TORSION_ANGLE_UPPER_LIMIT);
		} else {
			this.torsionAngle = torsionAnglevalue;
		}

	}

	/**
	 * Checks to make sure the parameters parsed into the constructor are
	 * correct.
	 * 
	 * @param endGroup1
	 *            - CMLMolecule representation of end group 1.
	 * @param repeatUnitA
	 *            - CMLMolecule representation of repeat unit A.
	 * @param repeatUnitB
	 *            - CMLMolecule representation of repeat unit B.
	 * @param endGroup2
	 *            - CMLMolecule representation of end group 2.
	 */
	private void checkCMLMoleculeParameterSetUp(final CMLMolecule endGroup1,
			final CMLMolecule repeatUnitA, final CMLMolecule repeatUnitB,
			final CMLMolecule endGroup2) {
		if (endGroup1 == null) {
			this.endGroup1Molecule = PolymerisationUtils
					.convertSmileString2CML("[R][H]", "Eg1");
		} else {
			if (PolymerisationUtils.getRElements(endGroup1).size() == 1) {
				this.endGroup1Molecule = PolymerisationUtils.addId(endGroup1,
						"Eg1");
			} else {
				throw new IllegalArgumentException(
						"End groups need to have 1 R group!");
			}
		}
		if (repeatUnitA == null) {
			throw new NullPointerException(
					"No repeat unit entered, please enter a repeat unit");
		} else {
			if (PolymerisationUtils.getRElements(repeatUnitA).size() == 2) {
				this.repeatUnitAMolecule = PolymerisationUtils.addId(
						repeatUnitA, "");
			} else {
				throw new IllegalArgumentException(
						"Repeat unit needs two R groups!");
			}
		}
		if (repeatUnitB == null) {
			throw new NullPointerException(
					"No repeat unit entered, please enter a repeat unit");
		} else {
			if (PolymerisationUtils.getRElements(repeatUnitB).size() == 2) {
				this.repeatUnitBMolecule = PolymerisationUtils.addId(
						repeatUnitB, "");
			} else {
				throw new IllegalArgumentException(
						"Repeat unit needs two R groups!");
			}
		}

		if (endGroup2 == null) {
			this.endGroup2Molecule = PolymerisationUtils
					.convertSmileString2CML("[R][H]", "Eg2");
		} else {
			if (PolymerisationUtils.getRElements(endGroup2).size() == 1) {
				this.endGroup2Molecule = PolymerisationUtils.addId(endGroup2,
						"Eg2");
			} else {
				throw new IllegalArgumentException(
						"End groups need to have 1 R group!");
			}
		}

	}

	/**
	 * Checks to make sure the parameters parsed into the constructor are
	 * correct.
	 * 
	 * @param endGroup1
	 *            - SMILE string representation of end group 1.
	 * @param repeatUnitA
	 *            - SMILE string representation of repeat unit A.
	 * @param repeatUnitB
	 *            - SMILE string representation of repeat unit B.
	 * @param endGroup2
	 *            - SMILE string representation of end group 2.
	 */
	private void checkStringMoleculeParameterSetUp(final String endGroup1,
			final String repeatUnitA, final String repeatUnitB,
			final String endGroup2) {
		if (endGroup1 == null) {
			this.endGroup1Molecule = PolymerisationUtils
					.convertSmileString2CML("[R][H]", "Eg1");
		} else {
			int rCount = PolymerisationUtils.countNumberofFragmentPoints(
					endGroup1, "[R]");
			if (rCount == 1) {
				this.endGroup1Molecule = PolymerisationUtils
						.convertSmileString2CML(endGroup1, "Eg1");
			} else if (rCount != 1) {
				throw new IllegalArgumentException(
						"End groups are only allowed one R group!");
			}
		}
		if (repeatUnitA == null) {
			throw new NullPointerException(
					"No repeat unit entered, please enter a repeat unit smile string");
		} else {
			int rCount = PolymerisationUtils.countNumberofFragmentPoints(
					repeatUnitA, "[R]");
			if (rCount == 2) {
				this.repeatUnitAMolecule = PolymerisationUtils
						.convertSmileString2CML(repeatUnitA, "");
			} else if (rCount != 2) {
				throw new IllegalArgumentException(
						"Repeat units must have two R-groups!");
			}
		}

		if (repeatUnitB == null) {
			throw new NullPointerException(
					"No repeat unit entered, please enter a repeat unit smile string");
		} else {
			int rCount = PolymerisationUtils.countNumberofFragmentPoints(
					repeatUnitB, "[R]");
			if (rCount == 2) {
				this.repeatUnitBMolecule = PolymerisationUtils
						.convertSmileString2CML(repeatUnitB, "");
			} else if (rCount != 2) {
				throw new IllegalArgumentException(
						"Repeat units must have two R-groups!");
			}
		}

		if (endGroup2 == null) {
			this.endGroup2Molecule = PolymerisationUtils
					.convertSmileString2CML("[R][H]", "Eg2");
		} else {
			int rCount = PolymerisationUtils.countNumberofFragmentPoints(
					endGroup2, "[R]");
			if (rCount == 1) {
				this.endGroup2Molecule = PolymerisationUtils
						.convertSmileString2CML(endGroup2, "Eg2");
			} else if (rCount != 1) {
				throw new IllegalArgumentException(
						"End groups are only allowed one R group!");
			}
		}

	}

	/**
	 * Adds RRBonds and joins up the separate CMLMolecule components.
	 * 
	 * @param repeatUnitARgroups
	 *            - ArrayList<ArrayList<CMLAtom>> represents all Rgroup CMLAtoms
	 *            in repeat unit A.
	 * @param rrBondJoinIds
	 *            - ArrayList<String> represents atom ids to form R-R bonds.
	 * @param repeatUnitBRgroups
	 *            - ArrayList<ArrayList<CMLAtom>> represents all Rgroup CMLAtoms
	 *            in repeat unit B.
	 * @param combiningAandB
	 *            - ArrayList<CMLMolecule> represents a all CMLMolecules in the
	 *            unjoined polymer to be joined.
	 * @param eg2R
	 *            - ArrayList<CMLAtom> represents Rgroup in end group two to
	 *            form an R-R bond with the last repeat unit.
	 */
	private void addRRBondsAndJoin(
			final ArrayList<ArrayList<CMLAtom>> repeatUnitARgroups,
			final ArrayList<String> rrBondJoinIds,
			final ArrayList<ArrayList<CMLAtom>> repeatUnitBRgroups,
			final ArrayList<CMLMolecule> combiningAandB,
			final ArrayList<CMLAtom> eg2R) {
		for (int i = 0; i < repeatUnitARgroups.size(); i++) {
			for (int j = 0; j < repeatUnitARgroups.get(i).size(); j++) {
				rrBondJoinIds.add(repeatUnitARgroups.get(i).get(j).getId());
			}
			for (int j = 0; j < repeatUnitBRgroups.get(i).size(); j++) {
				rrBondJoinIds.add(repeatUnitBRgroups.get(i).get(j).getId());
			}
		}
		rrBondJoinIds.add(eg2R.get(0).getId());
		joinMolecules(combiningAandB, rrBondJoinIds);
	}

	/**
	 * Generates a CMLFragment for PML document generation.
	 * 
	 * @return CMLFragment
	 */
	public final CMLFragment getFragment() {
		parentFragment = new CMLFragment();
		ArrayList<CMLAtom> eg1R = PolymerisationUtils
				.getRElements(endGroup1Molecule);
		ArrayList<CMLAtom> eg2R = PolymerisationUtils
				.getRElements(endGroup2Molecule);
		ArrayList<CMLMolecule> repeatUnitAMolecules = PolymerisationUtils
				.getRepeatUnitMolecules(
						numberOfAUnits * degreeOfPolymerisation,
						repeatUnitAMolecule, "RUA");
		ArrayList<CMLMolecule> repeatUnitBMolecules = PolymerisationUtils
				.getRepeatUnitMolecules(
						numberOfBUnits * degreeOfPolymerisation,
						repeatUnitBMolecule, "RUB");
		ArrayList<ArrayList<String>> rgroupRUAIds = PolymerisationUtils
				.getRgroupConnectors(repeatUnitAMolecules);
		ArrayList<ArrayList<String>> rgroupRUBIds = PolymerisationUtils
				.getRgroupConnectors(repeatUnitBMolecules);
		String moleculeRefs = "PREVIOUS NEXT";
		// End group 1
		CMLMolecule endGroup1ACBlank = new CMLMolecule();
		endGroup1ACBlank.setRef("Eg1");
		CMLFragment endGroup1ACBlankFragment = new CMLFragment();
		endGroup1ACBlankFragment.addMolecule(endGroup1ACBlank);
		CMLFragment endGroup2ACBlankFragment = new CMLFragment();
		CMLFragment parentACFragment = new CMLFragment();
		// Repeat unit A
		ArrayList<CMLMolecule> blankACRepeatUnitsA = new ArrayList<CMLMolecule>();
		ArrayList<CMLFragment> blankACFragmentsA = new ArrayList<CMLFragment>();
		// Repeat unit B
		ArrayList<CMLMolecule> blankACRepeatUnitsB = new ArrayList<CMLMolecule>();
		ArrayList<CMLFragment> blankACFragmentsB = new ArrayList<CMLFragment>();
		PolymerisationUtils.addBlankFragments(repeatUnitAMolecules,
				blankACRepeatUnitsA, repeatUnitBMolecules, blankACRepeatUnitsB,
				blankACFragmentsA, blankACFragmentsB);
		// End group 2
		CMLMolecule endGroupAC2Blank = new CMLMolecule();
		endGroupAC2Blank.setRef("Eg2");
		endGroup2ACBlankFragment.addMolecule(endGroupAC2Blank);
		// PolymerisationUtils.addBlankFragments(repeatUnitAMolecules,
		// repeatUnitBMolecules, endGroup1Blank, endGroup1BlankFragment,
		// blankRepeatUnitsA, blankFragmentsA, blankRepeatUnitsB,
		// blankFragmentsB);
		// Join A-A fragments
		ArrayList<CMLFragment> buildGraftFragments = new ArrayList<CMLFragment>();
		CMLFragment parentAAGraft = new CMLFragment();
		if (numberOfAUnits > 1) {
			int dpMultiplier = 1;
			for (int i = 0; i < repeatUnitAMolecules.size(); i++) {
				if (i == dpMultiplier * numberOfAUnits - 1) {
					dpMultiplier++;
					CMLFragment frag = new CMLFragment();
					frag.addMolecule(blankACRepeatUnitsA.get(i));
					parentAAGraft.appendChild(frag);
					buildGraftFragments.add(parentAAGraft);
					parentAAGraft = new CMLFragment();
				} else {
					CMLFragment frag = new CMLFragment();
					frag.addMolecule(blankACRepeatUnitsA.get(i));
					CMLJoin gJoin = new CMLJoin();
					gJoin.setAtomRefs2(rgroupRUAIds.get(i).get(1) + " "
							+ rgroupRUAIds.get(i + 1).get(0));
					gJoin.processMoleculeRefs2AndAtomRefs2(repeatUnitAMolecules
							.get(i), repeatUnitAMolecules.get(i + 1));
					gJoin.setMoleculeRefs2(moleculeRefs);
					parentAAGraft.appendChild(frag);
					parentAAGraft.addJoin(gJoin);
				}
			}
		}
		// Join BB fragments
		ArrayList<CMLFragment> buildGraftFragmentsB = new ArrayList<CMLFragment>();
		CMLFragment parentBBGraft = new CMLFragment();
		if (numberOfBUnits > 1) {
			int dpMultiplier = 1;
			for (int i = 0; i < repeatUnitBMolecules.size(); i++) {
				if (i == dpMultiplier * numberOfBUnits - 1) {
					dpMultiplier++;
					CMLFragment frag = new CMLFragment();
					frag.addMolecule(blankACRepeatUnitsB.get(i));
					parentBBGraft.appendChild(frag);
					buildGraftFragmentsB.add(parentBBGraft);
					parentBBGraft = new CMLFragment();
				} else {
					CMLFragment frag = new CMLFragment();
					frag.addMolecule(blankACRepeatUnitsB.get(i));
					CMLJoin gJoin = new CMLJoin();
					gJoin.setAtomRefs2(rgroupRUBIds.get(i).get(1) + " "
							+ rgroupRUBIds.get(i + 1).get(0));
					gJoin.processMoleculeRefs2AndAtomRefs2(repeatUnitBMolecules
							.get(i), repeatUnitBMolecules.get(i + 1));
					gJoin.setMoleculeRefs2(moleculeRefs);
					parentBBGraft.appendChild(frag);
					parentBBGraft.addJoin(gJoin);
				}
			}
		}

		// Join eg1 to AA1
		// Manually add join between end group 1 and repeatUnit(0)
		CMLJoin endGroup1ToRepeatUnit = new CMLJoin();
		String[] eg1JoinValues = new String[2];
		eg1JoinValues[0] = eg1R.get(0).getId();
		eg1JoinValues[1] = rgroupRUAIds.get(0).get(0);
		endGroup1ToRepeatUnit.setAtomRefs2(eg1JoinValues);
		endGroup1ToRepeatUnit.processMoleculeRefs2AndAtomRefs2(
				endGroup1ACBlank, repeatUnitAMolecules.get(0));
		endGroup1ToRepeatUnit.setMoleculeRefs2(moleculeRefs);
		parentACFragment.appendChild(endGroup1ACBlankFragment);
		parentACFragment.addJoin(endGroup1ToRepeatUnit);
		if (numberOfAUnits > 1) {
			parentACFragment.appendChild(buildGraftFragments.get(0));
		} else {
			parentACFragment.appendChild(blankACRepeatUnitsA.get(0));
		}
		// Add joins etc in alternating fashion after until DP is reached
		for (int i = 0; i < degreeOfPolymerisation; i++) {
			if (numberOfAUnits > 1 && numberOfBUnits == 1) {
				CMLJoin repeatUnitABJoin = new CMLJoin();
				repeatUnitABJoin.setAtomRefs2(rgroupRUAIds.get(
						(numberOfAUnits * (i + 1) - 1)).get(1)
						+ " " + rgroupRUBIds.get(i).get(0));
				repeatUnitABJoin.processMoleculeRefs2AndAtomRefs2(
						repeatUnitAMolecules.get((numberOfAUnits * i)),
						repeatUnitBMolecules.get(i));
				repeatUnitABJoin.setMoleculeRefs2(moleculeRefs);
				parentACFragment.appendChild(buildGraftFragments.get(i));
				parentACFragment.addJoin(repeatUnitABJoin);
				parentACFragment.appendChild(blankACFragmentsB.get(i));
				// Add join for BBB-AAA etc
				if (i <= degreeOfPolymerisation - 2) {
					CMLJoin repeatUnitBAJoin = new CMLJoin();
					repeatUnitBAJoin
							.setAtomRefs2(rgroupRUBIds.get(i).get(1)
									+ " "
									+ rgroupRUAIds
											.get(numberOfAUnits * (i + 1)).get(
													0));
					repeatUnitBAJoin.processMoleculeRefs2AndAtomRefs2(
							repeatUnitBMolecules.get(i + 1),
							repeatUnitAMolecules.get(numberOfAUnits * (i + 1)));
					repeatUnitBAJoin.setMoleculeRefs2(moleculeRefs);
					parentACFragment.addJoin(repeatUnitBAJoin);
				}
			} else if (numberOfBUnits > 1 && numberOfAUnits == 1) {
				CMLJoin repeatUnitABJoin = new CMLJoin();
				repeatUnitABJoin.setAtomRefs2(rgroupRUAIds.get(i).get(1) + " "
						+ rgroupRUBIds.get((numberOfBUnits * (i))).get(0));
				repeatUnitABJoin.processMoleculeRefs2AndAtomRefs2(
						repeatUnitAMolecules.get((i)), repeatUnitBMolecules
								.get(numberOfBUnits * i));
				repeatUnitABJoin.setMoleculeRefs2(moleculeRefs);
				parentACFragment.appendChild(blankACFragmentsA.get(i));
				parentACFragment.addJoin(repeatUnitABJoin);
				updateParentFragment(i, buildGraftFragmentsB, rgroupRUBIds,
						rgroupRUAIds, repeatUnitAMolecules,
						repeatUnitBMolecules, moleculeRefs);

			} else if (numberOfBUnits > 1 && numberOfAUnits > 1) {
				CMLJoin repeatUnitABJoin = new CMLJoin();
				repeatUnitABJoin.setAtomRefs2(rgroupRUAIds.get(
						(numberOfAUnits * (i + 1) - 1)).get(1)
						+ " " + rgroupRUBIds.get(numberOfBUnits * i).get(0));
				repeatUnitABJoin.processMoleculeRefs2AndAtomRefs2(
						repeatUnitAMolecules.get((numberOfAUnits * i)),
						repeatUnitBMolecules.get(i));
				repeatUnitABJoin.setMoleculeRefs2(moleculeRefs);
				parentACFragment.appendChild(buildGraftFragments.get(i));
				parentACFragment.addJoin(repeatUnitABJoin);
				parentACFragment.appendChild(buildGraftFragmentsB.get(i));
				// Add join for BBB-AAA etc
				// if (i <= degreeOfPolymerisation - 2) {
				// CMLJoin repeatUnitBAJoin = new CMLJoin();
				// repeatUnitBAJoin
				// .setAtomRefs2(rgroupRUBIds.get(
				// numberOfBUnits * (i + 1) - 1).get(1)
				// + " "
				// + rgroupRUAIds
				// .get(numberOfAUnits * (i + 1)).get(
				// 0));
				// repeatUnitBAJoin.processMoleculeRefs2AndAtomRefs2(
				// repeatUnitBMolecules.get(numberOfBUnits * (i + 1)
				// - 1), repeatUnitAMolecules
				// .get(numberOfAUnits * (i + 1)));
				// repeatUnitBAJoin.setMoleculeRefs2(moleculeRefs);
				// parentFragment.addJoin(repeatUnitBAJoin);
				// }
				updateParentFragment(i, buildGraftFragmentsB, rgroupRUBIds,
						rgroupRUBIds, blankACRepeatUnitsB, blankACRepeatUnitsB,
						moleculeRefs);

			} else {
				// if number of a and b units == 1
				CMLJoin repeatUnitABJoin = new CMLJoin();
				repeatUnitABJoin.setAtomRefs2(rgroupRUAIds.get(
						(numberOfAUnits * (i + 1) - 1)).get(1)
						+ " " + rgroupRUBIds.get(numberOfBUnits * i).get(0));
				repeatUnitABJoin.processMoleculeRefs2AndAtomRefs2(
						repeatUnitAMolecules.get((numberOfAUnits * i)),
						repeatUnitBMolecules.get(i));
				repeatUnitABJoin.setMoleculeRefs2(moleculeRefs);
				parentACFragment.appendChild(blankACFragmentsA.get(i));
				parentACFragment.addJoin(repeatUnitABJoin);
				// updateParentFragment(i,buildGraftFragmentsB,rgroupRUBIds,
				// rgroupRUAIds
				// ,repeatUnitAMolecules,repeatUnitBMolecules,moleculeRefs);
				parentACFragment.appendChild(blankACFragmentsB.get(i));
				// Add join for BBB-AAA etc
				if (i <= degreeOfPolymerisation - 2) {
					CMLJoin repeatUnitBAJoin = new CMLJoin();
					repeatUnitBAJoin
							.setAtomRefs2(rgroupRUBIds.get(
									numberOfBUnits * (i + 1) - 1).get(1)
									+ " "
									+ rgroupRUAIds
											.get(numberOfAUnits * (i + 1)).get(
													0));
					repeatUnitBAJoin.processMoleculeRefs2AndAtomRefs2(
							repeatUnitBMolecules.get(numberOfBUnits * (i + 1)
									- 1), repeatUnitAMolecules
									.get(numberOfAUnits * (i + 1)));
					repeatUnitBAJoin.setMoleculeRefs2(moleculeRefs);
					parentACFragment.addJoin(repeatUnitBAJoin);
				}
			}
		}
		// then join eg2 to BB
		// Add end group 2
		CMLJoin lastRepeatUnitToEndGroup = new CMLJoin();
		lastRepeatUnitToEndGroup.setAtomRefs2(rgroupRUBIds.get(
				rgroupRUBIds.size() - 1).get(1)
				+ " " + eg2R.get(0).getId());
		lastRepeatUnitToEndGroup.processMoleculeRefs2AndAtomRefs2(
				repeatUnitBMolecules.get(repeatUnitBMolecules.size() - 1),
				endGroupAC2Blank);
		lastRepeatUnitToEndGroup.setMoleculeRefs2(moleculeRefs);
		if (numberOfBUnits > 1) {
			parentACFragment.appendChild(buildGraftFragmentsB
					.get(buildGraftFragmentsB.size() - 1));
		} else {
			parentACFragment.appendChild(blankACFragmentsB
					.get(blankACFragmentsB.size() - 1));
		}
		parentACFragment.addJoin(lastRepeatUnitToEndGroup);
		parentACFragment.appendChild(endGroup2ACBlankFragment);
		return parentACFragment;
	}

	/**
	 * Updates parent fragment with build information.
	 * 
	 * @param i
	 *            - integer index.
	 * @param buildGraftFragmentsB
	 *            - ArrayList<CMLFragment>
	 * @param rgroupRUBIds
	 *            - ArrayList<ArrayList<String>>, ids of R-groups.
	 * @param rgroupRUAIds
	 *            - ArrayList<ArrayList<String>>, ids of R-groups.
	 * @param repeatUnitAMolecules
	 *            - ArrayList<CMLMolecule>.
	 * @param repeatUnitBMolecules
	 *            - ArrayList<CMLMolecule>.
	 * @param moleculeRefs
	 *            -String representing molecule refs to join.
	 */
	private void updateParentFragment(final int i,
			final ArrayList<CMLFragment> buildGraftFragmentsB,
			final ArrayList<ArrayList<String>> rgroupRUBIds,
			final ArrayList<ArrayList<String>> rgroupRUAIds,
			final ArrayList<CMLMolecule> repeatUnitAMolecules,
			final ArrayList<CMLMolecule> repeatUnitBMolecules,
			final String moleculeRefs) {
		parentFragment.appendChild(buildGraftFragmentsB.get(i));
		// Add join for BBB-AAA etc
		if (i <= degreeOfPolymerisation - 2) {
			CMLJoin repeatUnitBAJoin = new CMLJoin();
			repeatUnitBAJoin.setAtomRefs2(rgroupRUBIds.get(
					numberOfBUnits * (i + 1) - 1).get(1)
					+ " " + rgroupRUAIds.get(i + 1).get(0));
			repeatUnitBAJoin.processMoleculeRefs2AndAtomRefs2(
					repeatUnitBMolecules.get(numberOfBUnits * (i + 1) - 1),
					repeatUnitAMolecules.get(i + 1));
			repeatUnitBAJoin.setMoleculeRefs2(moleculeRefs);
			parentFragment.addJoin(repeatUnitBAJoin);
		}

	}
}
